#!/usr/bin/env python
'''
cluster-creator
Copyright 2009 Adam Drew

This file is part of cluster-creator.

cluster-creator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

cluster-creator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with cluster-creator.  If not, see <http://www.gnu.org/licenses/>.
'''


import os
import socket
import sys
import yum
from xml.dom.minidom import Document

'''
Cluster Setup Assitant

This siservicmple app will guide a user through a cluster setup process. 
Unlike our current tools it works in workflow controlled way.
After supporting many cluster suite users I've observed that the biggest problem
users have in setting up a new cluster is the lack of step-by-step
direction. System-config-cluster and Conga both allow for setting
up different componants in any order and neither tool (can) perform(s)
all of the steps truly required to get a cluster running.

The lack of a cluster setup "Wizard" has lead to the development of this tool.
'''

def remoteCommand(remoteCommand, textDescription, nodeIPs, nodeNames, nodeCount):
	for i in range(nodeCount):
		print "\n%s on %s. You may be asked for the node's root password multiple times." % (textDescription, nodeNames[i])
		os.system("ssh root@%s '%s'" % (nodeIPs[i], remoteCommand))
	
def clusterWideCommand(localCommand, textDescription, nodeIPs, nodeNames, nodeCount):
	for i in range(nodeCount):
		print textDescription % nodeNames[i]
		os.system(localCommand % nodeIPs[i])
	

def sanitizeName(objectName, maxLength):
	errorCode = 0

	#check for reserved charecters
	if objectName.find(" ") > 0:
		errorCode = 1

	#check for length
	if len(objectName) >= maxLength:
		errorCode = 2

	#return error code 		
	#0 == OK
	#1 == Invalid char
	#2 == Length exceeded
	return errorCode

#Get input on node count, node names, and node IPs
def gatherNodeInfo():
	nameSanity = 1
	while (nameSanity > 0):
		clusterName = raw_input("\nEnter cluster name : ")
		nameSanity = sanitizeName(clusterName, 15)
		if nameSanity > 0:
			print "Cluster names must be 15 chars or less and contain only letters, numbers, or hyphan. Please enter a valid name."
   
	nodeCount = int(raw_input("Enter desired number of nodes : "))
	nodeNames = [0]*nodeCount
	nodeIPs = [0]*nodeCount

	for i in range(nodeCount):
		nameSanity = 1
		while (nameSanity > 0):
			nodeNames[i] = raw_input("Enter desired name for node %s : "%str(i+1))
			nameSanity = sanitizeName(nodeNames[i], 32)
		if nameSanity > 0:
			print "Node names must be 32 chars or less and contain only letters, numbers, periods or hyphan. Please enter a valid name."

	for i in range(nodeCount):
		targetSucceed = 1
		while (targetSucceed == 1):
			nodeIPs[i] = raw_input("Enter IP address for %s : " %nodeNames[i] )
			print "Testing IP..."
			s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
			s.settimeout(2)
			try:
				s.connect((nodeIPs[i], 22))
				s.close()
			except:
				print "Target failed. Please enter a valid IP."
			else:
				targetSucceed = 0
				print "Target succeed"

	return clusterName, nodeCount, nodeNames,nodeIPs 	


def createClusterConf(clusterName, nodeIPs, nodeNames, nodeCount):
        cfg_cluster = Document()
        e_cluster = cfg_cluster.createElement("cluster")
        e_cluster.setAttribute("config_version", "1")
        e_cluster.setAttribute("name", clusterName)
        cfg_cluster.appendChild(e_cluster)
        e_fence_daemon = cfg_cluster.createElement("fence_daemon")
        e_fence_daemon.setAttribute("post_fail_delay", "0")
        e_fence_daemon.setAttribute("post_join_delay", "30")
        e_cluster.appendChild(e_fence_daemon)
        e_clusternodes = cfg_cluster.createElement("clusternodes")
        e_cluster.appendChild(e_clusternodes)
        e_clusternode = []
        e_fence = []
        for i in range(nodeCount):
                e_clusternode.append(cfg_cluster.createElement("clusternode"))
                e_fence.append(cfg_cluster.createElement("fence"))
                e_clusternode[i].setAttribute("name", nodeNames[i])
                e_clusternode[i].setAttribute("nodeid", str(i+1))
                e_clusternode[i].setAttribute("votes", "1")
                e_clusternodes.appendChild(e_clusternode[i])
                e_clusternode[i].appendChild(e_fence[i])
        e_cman = cfg_cluster.createElement("cman")
        if nodeCount == 2:
                e_cman.setAttribute("expected_votes", "1")
                e_cman.setAttribute("two_node", "1")
        else:
                e_cman.setAttribute("expected_votes", str(nodeCount))
        e_cluster.appendChild(e_cman)
        e_fencedevices = cfg_cluster.createElement("fencedevices")
        e_cluster.appendChild(e_fencedevices)
        e_rm = cfg_cluster.createElement("rm")
        e_cluster.appendChild(e_rm)
        e_failoverdomains = cfg_cluster.createElement("failoverdomains")
        e_rm.appendChild(e_failoverdomains)
        e_resources = cfg_cluster.createElement("resources")
        e_rm.appendChild(e_resources)
        f = open("/tmp/cluster.conf", "w")
        f.write(cfg_cluster.toprettyxml())
        f.close()
	
	
def verifyPackageGroups(nodeCount, nodeIPs):
	print "Verifying cluster and cluster storage packages are available"
	for i in range(nodeCount):
		clusteringGroupTest = os.system("ssh root@%s 'yum grouplist | grep Clustering > /dev/null'"  % nodeIPs[i], )
		clusterStorageGroupTest = os.system("ssh root@%s 'yum grouplist | grep Cluster | grep Storage > /dev/null'"  % nodeIPs[i], )
		if clusteringGroupTest != 0 or clusterStorageGroupTest != 0:
			print "Error! System does not have access to Clustering or Cluster Storage package groups"
			return 1
	return 0
			
	
def welcomeAndHandleAuthentication():
	print "Welcome to the Cluster setup assistant."
	print "--------------------------------------------------------------------------"
	print "This tool will take you through a series of steps to get a Linux"
	print "cluster up and running."
	print "\nThis tool will frequently access your cluster nodes while going"
	print "through the setup process. This will generate quite a few requests"
	print "for the root password on those nodes. Depending on the size of"
	print "your prospective cluster it may generate many dozens of requests."
	print "\nIt is reccomended that you set up SSH automatic login between the cluster"
	print "nodes and this machine (if this machine is not part of the cluster."
	print "This tool can assist you with that process, you can do it yourself,"
	print "or you can just continue if you have already set this up or you don't care."
	print "----------------------------------------------------------------------------"
	print "1\tSet up automatic SSH for me"
	print "2\tContinue with Cluster setup"
	print "3\tExit"
	while 1:
		selection = raw_input("Please select 1, 2, or 3 : ")
		if  selection == "1":
			setupSSH()
			return
		if selection == "2":
			return
		if selection == "3":
			sys.exit()
			

def main():
	welcomeAndHandleAuthentication()
	
	clusterName, nodeCount, nodeNames, nodeIPs = gatherNodeInfo()
	
	#Have user verify current node name mappings
	isCorrect = "A"	
	while isCorrect != "Y":
		for i in range(nodeCount):
			nameMapping = nodeIPs[i] + "     " + nodeNames[i]
			print nameMapping
		isCorrect = str(raw_input("\nIs this name-to-address mapping correct? (y/n)")).capitalize()
		if isCorrect == "N":
			main()
		elif isCorrect == "Y":
			break
		else:
			print "Please enter y or n"
	
	#Verify that nodes can access packages:
	if verifyPackageGroups(nodeCount, nodeIPs) ==1:
		print "One or more of the proposed cluster nodes cannot access the Clustering or Cluster Storage package groups."
		print "If this is a Red Hat Enterprise Linux system make sure the system is registerd to RHN or an RHN Satellite and"
		print "that it is subscribed to the Clustering and Cluster Storage channels on RHN or you RHN Satellite."
		print "If this is a Fedora, CentOS or other OS please verify your yum and repository configuration."
		sys.exit()
    
    #Delete temp file if it already exists
	if os.path.exists("/tmp/cluster.hosts.out"):
		os.system("rm -rf /tmp/cluster.hosts.out")
	
	#Iterate through nodes and set up hosts temp file
	for i in range(nodeCount):
		hostLine = nodeIPs[i] + "     " + nodeNames[i]
		os.system("echo '%s' >> /tmp/cluster.hosts.out" %hostLine)
	
	#copy hosts tmp file to nodes
	clusterWideCommand("scp /tmp/cluster.hosts.out root@%s:/tmp/ > /dev/null", "Putting mapping table on %s", nodeIPs, nodeNames, nodeCount)
	
	#update hosts file on nodes
	remoteCommand("cat /tmp/cluster.hosts.out >> /etc/hosts", "Updating hosts file",  nodeIPs, nodeNames, nodeCount)
	
	#install packages
	remoteCommand("yum -y -q groupinstall Cluster-Storage Clustering", "Installing cluster packages", nodeIPs, nodeNames, nodeCount)
	
	#disable troublesome services
	remoteCommand("chkconfig iptables off ; service iptables stop ; cp /etc/selinux/config /etc/selinux/selinux.config.backup.clusterinstall ; rm -rf /etc/selinux/config ; echo -e \"#Disabled SELinux config file created by cluster config script.\n\nSELINUX=disabled\nSELINUXTYPE=targeted\nSETLOCALDEFS=0\" > /etc/selinux/config", "Disabling SELinux and IPTables", nodeIPs, nodeNames, nodeCount)
	
	#Create cluster.conf temp file on the box executing the script
	createClusterConf(clusterName, nodeIPs, nodeNames, nodeCount)

	#Transmit the cluster.conf file to the cluster nodes
	clusterWideCommand("scp /tmp/cluster.conf root@%s:/etc/cluster/ > /dev/null", "Putting cluster config file on %s", nodeIPs, nodeNames, nodeCount)
	
	#Set cluster services to start on boot:
	remoteCommand("chkconfig cman on", "Enabling cluster services", nodeIPs, nodeNames, nodeCount)
	
	#Reboot the cluster nodes
	remoteCommand("init 6", "Pressing reboot", nodeIPs, nodeNames, nodeCount)
	
	#Clean up
	os.system("rm -rf /tmp/cluster*")
	
	#All done!
if __name__ == '__main__':
	main()
